import pygame,sys
from pygame.locals import *
from enum import Enum,unique
from math import sqrt
from random import randint

#孢子类
class Item(object):
    def __init__(self,x,y,color,radius):  #构造方法，初始化孢子对象属性
        self.x=x                       #设定孢子的坐标位置
        self.y=y
        self.color=color
        self.radius=radius
        self.alive=True
        self.id=2                      #设定孢子球体id类型为1
    def draw(self,screen):               #显示孢子方法
        pygame.draw.circle(screen,self.color, (self.x, self.y),self.radius, 0)

#敌方球体类
class Ball(object):
    def __init__(self,x,y,radius,sx,sy,image_num):    #构造方法，初始化敌方球体对象属性
        self.x = x                 #初始化敌方的坐标位置
        self.y = y
        self.radius = radius       #初始化敌方的半径大小
        self.sx = sx               #初始化敌方的横向移动速度
        self.sy = sy               #初始化敌方的纵向移动速度
        self.alive = True          #初始化敌方的生命状态alive为True
        self.image_num = image_num    #随机产生敌方的皮肤编号
        self.change_radius=True
        self.id=1                  #设定敌方球体id类型为1
        self.image = pygame.image.load("./skin_type/" + str(self.image_num) + ".png")   #指定敌方的皮肤显示图片
    def draw(self,screen):         #敌方球体的显示方法
        pygame.draw.circle(screen, (0, 0, 0), (self.x, self.y), self.radius, 0)   #先创建敌方球体的本体
        if self.change_radius == True:                                            #当半径发生变化后，调整皮肤图片尺寸
            self.image = pygame.image.load("./skin_type/" + str(self.image_num) + ".png")    #加载皮肤图片
            self.image = pygame.transform.smoothscale(self.image, (2 * self.radius, 2 * self.radius))  #调整皮肤尺寸
            self.change_radius=False
        else:
            pass
        screen.blit(self.image, (self.x - self.radius, self.y - self.radius))     #在本体上用皮肤图片进行覆盖
    def move(self,screen):         #敌方球体的移动方法
        self.x += self.sx          #位置随速度变化
        self.y += self.sy
        if self.x - self.radius <= 0 or self.x +self.radius >= screen.get_width():   #临界判断，若临界，则反向
            self.sx = -self.sx
        if self.y - self.radius <=0 or self.y + self.radius >= screen.get_height():
            self.sy = -self.sy
    def eat(self,other):           #敌方球体的吞噬方法
        if self.alive and other.alive and self!=other:    #遍历除自身外所有球体
            dx,dy = self.x -other.x,self.y - other.y
            distance = sqrt(dx**2 + dy**2)                #获取两球体间距
            if other.id == 1:                             #球体类型为：1，为Ball（敌方）类
                if distance <= self.radius - other.radius:  #自身体重较大，进行吞噬
                    other.alive = False
                    self.radius = self.radius + int(other.radius * 0.188)
                    self.change_radius = True
                elif distance <= other.radius - self.radius:  #自身体重较小，失活被吞噬
                    self.alive = False
                    other.radius = other.radius + int(self.radius * 0.188)
                    self.change_radius = True
                    other.change_radius = True
            elif other.id == 2:                           #球体类型为：2，为Item（孢子）类
                if distance <= self.radius - other.radius:
                    other.alive = False
                    self.radius = self.radius + int(other.radius * 0.255)
                    self.change_radius = True
            elif other.id == 3:                           #球体类型为：3，为Bomb（炸弹）类
                if distance <= self.radius - other.radius:  #自身体重较大，进行吞噬
                    other.alive = False
                    self.radius = int(self.radius / 1.5)
                    ball = Ball(self.x + int(self.radius * 1.2), self.y, int(self.radius / 1.5), self.sx, self.sy,
                                randint(1, 35))
                    balls.append(ball)
                    self.change_radius = True
            if self.change_radius == True:               #当体重发生变化时，改变运动轨迹，增加游戏趣味性
                speed_direct1 = randint(0, 1)
                speed_direct2 = randint(0, 1)
                self.sx = (1030 - 5 * randint(self.radius, 180)) / 1000
                self.sy = sqrt(((1030 - 5 * self.radius) / 1000) ** 2 - ((1030 - 5 * randint(self.radius, 180)) / 1000) ** 2)
                if speed_direct1 > 0.5:
                    self.sx *= -1
                if speed_direct2 > 0.5:
                    self.sy *= -1

#玩家球体类
class player(object):
    def __init__(self,x,y,radius,sx,sy,color):     #构造方法，初始化玩家球体对象属性
        self.x=x
        self.y=y
        self.radius=radius
        self.sx=sx
        self.sy=sy
        self.color=color
        self.alive=True
        self.old_act=0
        self.grade=0                              #初始化游戏成绩从0开始
        self.image=pygame.image.load("./skin_type/player.png")
        self.change_radius=True
    def draw(self,screen):
        pygame.draw.circle(screen, (0, 0, 0), (self.x, self.y), self.radius, 0)
        if self.change_radius == True:                                   #当球体体重发生变化时调整图片尺寸
            self.image = pygame.image.load("./skin_type/player.png")     #指定玩家球体皮肤图片
            self.image = pygame.transform.smoothscale(self.image, (2 * self.radius, 2 * self.radius))
            self.change_radius = False
        else:
            pass
        screen.blit(self.image, (self.x - self.radius, self.y - self.radius))
        self.sx=(1030-5*self.radius)/1000                                #球体速度体重成反比
        self.sy = (1030 - 5 * self.radius) / 1000
    def down(self,screen):                                               #玩家球体向下移动
        self.y += self.sy
        if self.y + self.radius < screen.get_height():
            pass
        else:
            self.y -= self.sy
    def up(self,screen):                                                 #玩家球体向上移动
        self.y -= self.sy
        if self.y - self.radius > 0:
            pass
        else:
            self.y += self.sy
    def right(self,screen):                                              #玩家球体向右移动
        self.x += self.sx
        if self.x + self.radius < screen.get_width():
            pass
        else:
            self.x -= self.sx
    def left(self,screen):                                               #玩家球体向左移动
        self.x -= self.sx
        if self.x - self.radius > 0:
            pass
        else:
            self.x += self.sx
    def out_item(self,screen):                                           #玩家进行吐孢子操作
        s = pygame.mixer.Sound('./sound/build_item.wav')                 #吐孢子时播放音效
        s.play()
        if self.old_act == 0 or self.radius <= 5:                        #球体体重过小不允许进行吐孢子操作
            pass
        elif self.old_act == 1 and self.y >= 30 + self.radius and self.radius > 5:  #向上一次移动的方向进行吐孢子操作
            item = Item(self.x, self.y - self.radius - 15, (randint(0, 255), randint(0, 255), randint(0, 255)), 4)
            items.append(item)
            self.radius = self.radius - 1
        elif self.old_act == 2 and self.y <= 570 - self.radius and self.radius > 5:
            item = Item(self.x, self.y + self.radius + 15, (randint(0, 255), randint(0, 255), randint(0, 255)), 4)
            items.append(item)
            self.radius = self.radius - 1
        elif self.old_act == 3 and self.x >= 30 + self.radius and self.radius > 5:
            item = Item(self.x - self.radius - 15, self.y, (randint(0, 255), randint(0, 255), randint(0, 255)), 4)
            items.append(item)
            self.radius = self.radius - 1
        elif self.old_act == 4 and self.x <= 670 - self.radius and self.radius > 5:
            item = Item(self.x + self.radius + 15, self.y, (randint(0, 255), randint(0, 255), randint(0, 255)), 4)
            items.append(item)
            self.radius = self.radius - 1
        else:
            pass
        self.change_radius = True
        pygame.time.delay(50)
    def eat(self,other):                                               #遍历其他球体，进行吞噬操作
        if self.alive == True and other.alive == True:
            dx,dy=self.x-other.x,self.y-other.y
            distance = sqrt(dx**2 + dy**2)
            if other.id == 1:
                if distance <= self.radius - other.radius :
                    other.alive = False
                    self.radius = self.radius + int(other.radius *0.188)
                    self.change_radius=True
                    s = pygame.mixer.Sound('./sound/eat_ball.wav')
                    s.play()
                    self.grade+=1
                elif distance <= other.radius - self.radius:            #当体重较小时，玩家球体被吞噬，失活且游戏结束
                    self.alive = False
                    other.radius = other.radius + int(self.radius * 0.188)
            elif other.id == 2:
                if distance <= self.radius - other.radius :
                    other.alive = False
                    self.radius = self.radius + int(other.radius *0.255)
                    self.change_radius=True
            elif other.id == 3:
                if distance <= self.radius - other.radius :
                    other.alive = False
                    self.radius = int(self.radius / 1.5)
                    ball = Ball(self.x+int(self.radius * 1.2), self.y, int(self.radius / 1.5), self.sx, self.sy, randint(1,35))
                    balls.append(ball)                                       #吞噬炸弹后将球体体重变小，并生成一个敌方球体
                    self.change_radius=True
                    s = pygame.mixer.Sound('./sound/boom.wav')               #吞噬炸弹播放爆炸音效
                    s.play()

#炸弹类
class Bomb(object):
    def __init__(self,x,y,radius,color):    #构造方法，初始化炸弹球体对象属性
        self.x=x
        self.y=y
        self.radius=radius
        self.color=color
        self.alive=True
        self.id=3
        self.image = pygame.image.load("./skin_type/bomb.png")

    def draw(self, screen):                 #炸弹球体显示方法，显示指定炸弹图片
        pygame.draw.circle(screen, self.color, (self.x, self.y), self.radius, 0)
        self.image = pygame.transform.smoothscale(self.image, (2 * self.radius, 2 * self.radius))
        screen.blit(self.image, (self.x - self.radius, self.y - self.radius))



def build_enemy(num,max):
    for i in range(num):
        if max > 10:
            radius = randint(10, max)
        else:
            radius = randint(max, 12)
        x, y = randint(radius, 800-radius), randint(radius, 600-radius)
        speed_direct1 = randint(0, 1)
        speed_direct2 = randint(0, 1)
        sx = (1030 - 5 * randint(radius,180)) / 1000
        sy = sqrt(((1030 - 5 * radius) / 1000)**2 - ((1030 - 5 * randint(radius,180)) / 1000)**2)
        if speed_direct1 > 0.5:
            sx *= -1
        if speed_direct2 > 0.5:
            sy *= -1
        skin_num = randint(1, 35)
        ball = Ball(x, y, radius, sx, sy, skin_num)
        balls.append(ball)

def build_item(num,radius):
    for i in range(num):
        item = Item(randint(2, 798), randint(2, 598), (randint(0, 255), randint(0, 255), randint(0, 255)),radius)
        items.append(item)

def build_bomb(num):
    for i in range(num):
        bomb = Bomb(randint(50, 750), randint(50, 550), randint(8, 16), (255, 0, 0))
        bombs.append(bomb)

#玩家输入键盘扫描
def player_action(player,screen):
    keys_pressed = pygame.key.get_pressed()   #获取按下按键值
    if keys_pressed[pygame.K_w]:              #按下 W 键，向上移动
        player.old_act=1
        player.up(screen)
    elif keys_pressed[pygame.K_s]:            #按下 S 键，向下移动
        player.old_act=2
        player.down(screen)
    elif keys_pressed[pygame.K_a]:            #按下 A 键，向左移动
        player.old_act=3
        player.left(screen)
    elif keys_pressed[pygame.K_d]:            #按下 D 键，向左移动
        player.old_act=4
        player.right(screen)
    elif keys_pressed[pygame.K_SPACE]:        #按下 SPACE 键，进行吐孢子操作
        player.out_item(screen)


balls=[]
bombs=[]
items=[]

def main():
    pygame.init()                                            #pygame初始化
    screen = pygame.display.set_mode((800,600))              #设置屏幕显示尺寸
    pygame.display.set_caption("球球大作战")                   #设置游戏名称
    pygame.mixer.init()                                      #音乐播放器初始化
    pygame.mixer.music.load("./sound/background_voice.wav")  #设置游戏背景音乐
    pygame.mixer.music.play()
    pygame.mixer.music.set_volume(0.45)                      #设置游戏音量
    running = True                                           #游戏运行标志
    birth=0                                                  #用于定时随机生成孢子、指定和敌方
    player1 = player(15,15,15,0,0,(0,0,0))                   #玩家对象实例化
    background=pygame.image.load("./skin_type/background.png")  #设置游戏背景图片
    font = pygame.font.Font('./font/FZXingHeiJW-R.TTF', 20)     #设置游戏显示文字字体
    build_item(500,2)                                           #开局生成500个孢子、50个敌方以及20个炸弹
    build_enemy(50, 20)
    build_bomb(20)
    while running:
        screen.blit(background, (0, 0))                         #屏幕刷新

        player_action(player1, screen)                          #监听玩家输入

        for event in pygame.event.get():
            if event.type == QUIT:
                running=False

        birth+=1
        if birth % 50 == 0:                                     #定时产生除玩家外其他球体
            build_item(50,2)
        if birth == 500:
            birth =0
            if player1.radius <=100:
                build_enemy(3,2+int(player1.radius*1.2))
            build_bomb(1)

        for bomb in bombs:                                      #遍历炸弹，进行显示及判断是否与玩家球体内切
            if bomb.alive:
                bomb.draw(screen)
                player1.eat(bomb)
            else:                                               #失活则删除该球体
                bombs.remove(bomb)
        for item in items:                                      #遍历孢子，进行显示及判断是否与玩家球体内切
            if item.alive:
                item.draw(screen)
                player1.eat(item)
            else:                                               #失活则删除该球体
                items.remove(item)
        for ball in balls:                                      #遍历敌方球体，进行显示及判断是否与玩家或其他敌方球体内切
            if ball.alive:
                ball.draw(screen)
            else:
                balls.remove(ball)
            ball.move(screen)
            for other in balls:
                ball.eat(other)
                player1.eat(other)
            for item in items:
                ball.eat(item)
            for bomb in bombs:
                ball.eat(bomb)

        if player1.alive == True:                               #若玩家球体未失活，游戏继续
            player1.draw(screen)
        elif player1.alive == False:                            #否则游戏结束
            print("游戏结束,成绩为 "+str(player1.grade))
            running = False

        if pygame.mixer.music.get_busy():                       #判断音乐是否还在播放
            pass
        else:                                                   #循环播放
            pygame.mixer.music.play()

        text = font.render('grade: '+str(player1.grade), True, (255,255,255), (20, 20, 20))   #设置文本显示玩家得分
        screen.blit(text, (380, 0))
        pygame.display.flip()
        pygame.display.update()
        pygame.time.delay(15)

if __name__ == '__main__':
    main()
